/*
 * Copyright 2014 by État de Genève (Switzerland)
 * All rights reserved.
 *
 * Author: ELCA Informatique SA
 */
define(['i18n', 'services/notifier', 'services/layout'], function (i18n, notifier, L) {

    var _dirs = {
        ASC: 'ASC',
        DESC: 'DESC',
        invert: {
            ASC: 'DESC',
            DESC: 'ASC'
        }
    };

    // cache for dbl click
    var _itemForDblClick = null;

    var detailTable = function () {

        // rows and columns of the table
        this.rows = ko.observableArray();
        this.columns = null;

        // table specifications (column type and format, actions, etc..)
        this.specs = null;

        // keep selected rows references
        this.selectedRows = ko.observableArray();

        this.sorting = ko.mapping.fromJS({
            id: '',
            dir: _dirs.ASC
        });

        // reference to commands
        this.commands = ko.observableArray();
        this.commandsCallback = {};
    };

    // called when 1 of the button is pressed above the table
    detailTable.prototype.command = command;
    // a link in one of the row is clicked
    detailTable.prototype.linkClick = linkClick;
    // a row is clicked
    detailTable.prototype.itemClick = itemClick;
    // tells rows if they're selected or not
    detailTable.prototype.isItemSelected = isItemSelected;
    detailTable.prototype.isItemCanceled = isItemCanceled;

    // tells columns if they're sorted and by what
    detailTable.prototype.isSortedBy = isSortedBy;
    // happens when a columns is clicked
    detailTable.prototype.orderBy = orderBy;
    // sort the underlying items
    detailTable.prototype.sort = sort;

    detailTable.prototype.selectionMismatch = selectionMismatch;
    detailTable.prototype.selectionNotExpected = selectionNotExpected;

    detailTable.prototype.activate = viewActivate;

    return detailTable;

    function selectionMismatch(expectedNb) {
        return !!expectedNb && expectedNb > 0 && expectedNb !== this.selectedRows().length;
    }

    // Disable button command if the selected row
    function selectionNotExpected(data) {
        if (typeof data.expected === 'function') {
            return data.expected(this.selectedRows());
        }
        return false;
    }

    function viewActivate (settings) {

        L.needsRelayout();

        if(!settings || !settings.specs || !settings.specs.columns) {
            this.rows([]);
            return;
        }

        this.settings = settings;
        this.specs = settings.specs;
        this.columns = settings.specs.columns;
        if(ko.isObservable(settings.items)) {
            this.rows = settings.items;
        } else {
            this.rows(settings.items || []);
        }

        var errRows = [];
        // for validation handling, we must have a path, and errors array must have at least one element
        if(this.settings.errors && this.settings.path && this.settings.errors.length > 0) {
            // get all rows index that are invalids
            errRows = _.uniq(_.map(this.settings.errors, function(validationError) {
                var path = typeof validationError === 'string' ? validationError : validationError.path;

                if(path && path.indexOf(this.settings.path + '[') === 0) {
                    return parseInt(/^[^\[]+\[(\d+)\]/.exec(path)[1], 10);
                } else {
                    return -1;
                }
            }.bind(this)));
        }

        // setup valid flag
        // use a function here to avoid poluting entities when serialized back to JSON
        _.each(this.rows(), function(item, index) {
            item.isValid = function () { return !_.contains(errRows, index); };
        });


        // find the ordering specs or define a new one
        if (this.specs.order && this.specs.order.id && this.specs.order.dir) {
            this.sorting = ko.mapping.fromJS(this.specs.order);
        } else {
            this.sorting = ko.mapping.fromJS({
                id: this.columns[0].id,
                dir: _dirs.ASC
            });
        }

        // sort the items
        this.sort();

        // prepare the commands for callbacks
        if (settings.specs.commands) {
            this.commands(settings.specs.commands);

            this.commandsCallback = _.object(_.map(settings.specs.commands, function(cmd) {
                return [cmd.id, cmd.callback];
            }));
        }
    }

    /**
     * do a basic javascript sort on the underlying items
     */
    function sort() {
        if(this.rows()) {
            this.rows(_.sortBy(ko.mapping.toJS(this.rows()), this.sorting.id()));
            if (this.sorting.dir() === _dirs.DESC) {
                this.rows(this.rows().reverse());
            }
        }
    }

    /**
     * tells if columns is sorted by dir
     * @param id
     * @param dir
     * @returns {boolean}
     */
    function isSortedBy(id, dir) {
        return this.sorting.id() === id && this.sorting.dir() === dir;
    }

    /**
     * change ordering of items (called on column header click)
     * @param column
     */
    function orderBy(column) {

        if (this.sorting.id() === column.id) {
            this.sorting.dir(_dirs.invert[this.sorting.dir()]);
            // reverse array
            this.rows(this.rows().reverse());
        } else {
            this.sorting.id(column.id);
            this.sort();
        }
    }

    /**
     * Tells if row is selected
     * @param item
     * @returns {boolean}
     */
    function isItemSelected(item) {
        return _.indexOf(this.selectedRows(), item) >= 0;
    }

    function isItemCanceled(item) {
        return (item.hasOwnProperty('annuler') && item.annuler);
    }

    /**
     * click on a table row
     * @param item
     * @param col
     */
    function itemClick(item, col) {

        if(_itemForDblClick && _itemForDblClick === item) {
            // just had a dbl click => do default action ('open' ?) then return!
            this.selectedRows([item]);
            this.command('open');
            return;
        }

        // detect dbl clicks by caching the last clicked element for 400ms
        _itemForDblClick = item;
        setTimeout(function() {
            _itemForDblClick = null;
        }, 400);

        // search item in selected rows
        var index = _.indexOf(this.selectedRows(), item);

        // only one item is selectable at a time: use the commented line for multiple select
        if (index >= 0) {
            // found: remove it from the selected rows,
            // this.selectedRows.splice(index, 1);
            this.selectedRows([]);
        } else {
            // not found: add it to the selected rows
            // this.selectedRows.push(item);
            this.selectedRows([item]);
        }
    }

    /**
     * if a command is clicked, call the corresponding callback
     * @param commandName
     */
    function command(commandName) {
        if (this.commandsCallback[commandName]) {
            this.commandsCallback[commandName](this.selectedRows());
        }
    }

    /**
     * What happens when a link inside a table is clicked ?
     * @param item
     * @param col
     */
    function linkClick(item, col) {
        if(item && col) {
            var linkDto = item[col.id];

            // do processing depending on linkDto type and id

            if(linkDto && linkDto.type === 'readFile') {
                // it's a document, we should open it in a new window
                window.open('api/readFile/' + linkDto.id);
            }
        }
        notifier.log('NOT FULLY IMPLEMENTED');
    }
});